package lang.asm;

import lang.Loader;
import model.Project;
import model.Segment;
import model.code.*;
import model.code.line.ASMLine;
import model.code.line.Line;
import model.code.line.MultiLineList;
import model.symbol.*;
import util.ArrayUtil;
import util.IntList;
import util.Pair;
import util.StringUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Pattern;

/**
 * This is the multi-pass loader for assembly language.
 *
 * @author Neil Dickson
 */
public class ASMLoad extends Loader {
	public static final ASMLoad instance = new ASMLoad();

	public final	HashMap<String,Keyword>	preprocessorKeywords;
	public final	HashMap<String,Keyword>	defaultSegmentKeywords;
	public final	HashMap<String,Keyword> dataDeclarationKeywords;
	public final	HashMap<String,Keyword>	allKeywords;


	private ASMLoad() {
		preprocessorKeywords = new HashMap<String,Keyword>();
		defaultSegmentKeywords = new HashMap<String,Keyword>();
		dataDeclarationKeywords = new HashMap<String,Keyword>();
		allKeywords = new HashMap<String, Keyword>();
		defaultSegmentKeywords.put(ASM.CODE_SEGMENT.getName().toString(),ASM.CODE_SEGMENT);
		defaultSegmentKeywords.put(ASM.INIT_DATA_SEGMENT.getName().toString(),ASM.INIT_DATA_SEGMENT);
		defaultSegmentKeywords.put(ASM.UNINIT_DATA_SEGMENT.getName().toString(),ASM.UNINIT_DATA_SEGMENT);
		defaultSegmentKeywords.put(ASM.READ_ONLY_DATA_SEGMENT.getName().toString(),ASM.READ_ONLY_DATA_SEGMENT);
		dataDeclarationKeywords.put(ASM.DB.getName().toString(),ASM.DB);
		dataDeclarationKeywords.put(ASM.DW.getName().toString(),ASM.DW);
		dataDeclarationKeywords.put(ASM.DD.getName().toString(),ASM.DD);
		dataDeclarationKeywords.put(ASM.DF.getName().toString(),ASM.DF);
		dataDeclarationKeywords.put(ASM.DQ.getName().toString(),ASM.DQ);
		dataDeclarationKeywords.put(ASM.DT.getName().toString(),ASM.DT);
		dataDeclarationKeywords.put(ASM.SBYTE.getName().toString(),ASM.SBYTE);
		dataDeclarationKeywords.put(ASM.SWORD.getName().toString(),ASM.SWORD);
		dataDeclarationKeywords.put(ASM.SDWORD.getName().toString(),ASM.SDWORD);
		dataDeclarationKeywords.put(ASM.BYTE.getName().toString(),ASM.BYTE);
		dataDeclarationKeywords.put(ASM.WORD.getName().toString(),ASM.WORD);
		dataDeclarationKeywords.put(ASM.DWORD.getName().toString(),ASM.DWORD);
		dataDeclarationKeywords.put(ASM.FWORD.getName().toString(),ASM.FWORD);
		dataDeclarationKeywords.put(ASM.QWORD.getName().toString(),ASM.QWORD);
		dataDeclarationKeywords.put(ASM.TBYTE.getName().toString(),ASM.TBYTE);
		dataDeclarationKeywords.put(ASM.XMMWORD.getName().toString(),ASM.XMMWORD);
		dataDeclarationKeywords.put(ASM.REAL4.getName().toString(),ASM.REAL4);
		dataDeclarationKeywords.put(ASM.REAL8.getName().toString(),ASM.REAL8);
		dataDeclarationKeywords.put(ASM.REAL10.getName().toString(),ASM.REAL10);
		dataDeclarationKeywords.put(ASM.PTR.getName().toString(),ASM.PTR);
		
		allKeywords.put(Directive.AND.getName().toString(),Directive.AND);
		allKeywords.put(Directive.OR.getName().toString(),Directive.OR);
		allKeywords.put(Directive.XOR.getName().toString(),Directive.XOR);
		allKeywords.put(Directive.SHL.getName().toString(),Directive.SHL);
		allKeywords.put(Directive.SHR.getName().toString(),Directive.SHR);
		allKeywords.put(Directive.NOT.getName().toString(),Directive.NOT);
		allKeywords.put(Directive.LOW.getName().toString(),Directive.LOW);
		allKeywords.put(Directive.HIGH.getName().toString(),Directive.HIGH);
		allKeywords.put(Directive.LOWWORD.getName().toString(),Directive.LOWWORD);
		allKeywords.put(Directive.HIGHWORD.getName().toString(),Directive.HIGHWORD);
		allKeywords.put(Directive.LOG2.getName().toString(),Directive.LOG2);
		allKeywords.put(Directive.SIZEOF.getName().toString(),Directive.SIZEOF);
		allKeywords.put(Directive.OFFSET.getName().toString(),Directive.OFFSET);
		allKeywords.put(Directive.EQ.getName().toString(),Directive.EQ);
		allKeywords.put(Directive.NE.getName().toString(),Directive.NE);
		allKeywords.put(Directive.LT.getName().toString(),Directive.LT);
		allKeywords.put(Directive.GT.getName().toString(),Directive.GT);
		allKeywords.put(Directive.LE.getName().toString(),Directive.LE);
		allKeywords.put(Directive.GE.getName().toString(),Directive.GE);

		allKeywords.put(ASM.REGISTER.getName().toString(),ASM.REGISTER);
		allKeywords.put(ASM.REQ.getName().toString(),ASM.REQ);
		allKeywords.put(ASM.INVOKE.getName().toString(),ASM.INVOKE);
		allKeywords.put(ASM.UNDEFINED_VALUE.getName().toString(),ASM.UNDEFINED_VALUE);
		allKeywords.put(ASM.DUP.getName().toString(),ASM.DUP);
		allKeywords.put(ASM.AT_F.getName().toString(),ASM.AT_F);
		allKeywords.put(ASM.AT_B.getName().toString(),ASM.AT_B);
		allKeywords.put(ASM.AT_AT.getName().toString(),ASM.AT_AT);
		allKeywords.put(ASM.ASSUME.getName().toString(),ASM.ASSUME);
		allKeywords.put(ASM.NOTHING.getName().toString(),ASM.NOTHING);
		preprocessorKeywords.put(ASM.IF.getName().toString(),ASM.IF);
		preprocessorKeywords.put(ASM.ELSEIF.getName().toString(),ASM.ELSEIF);
		preprocessorKeywords.put(ASM.ELSE.getName().toString(),ASM.ELSE);
		preprocessorKeywords.put(ASM.ENDIF.getName().toString(),ASM.ENDIF);
		allKeywords.put(ASM.ERR.getName().toString(),ASM.ERR);
		allKeywords.putAll(preprocessorKeywords);
		allKeywords.putAll(defaultSegmentKeywords);
		allKeywords.putAll(dataDeclarationKeywords);
		ASM.language.getScope().addAll(allKeywords.values());
		ASM.language.getScope().add(ASM.HERE);
	}

	/**
	 * @return true, because the assembly language loading goes line by line
	 */
	public boolean splitLinesFirst() {
		return true;
	}

	/**
	 * This initial scan checks for includes and comments.
	 * @param data the data to be parsed
	 * @param allData list of all files being parsed
	 * @param project the current project, whose global {@link Scope} has notification turned off during load
	 */
	public void initialScan(FileLoadingData data,ArrayList<FileLoadingData> allData, Project project) {
		ArrayList<Object> content = data.getContent();
		String[] text = (String[])content.remove(0);
		String[] code = new String[text.length];
		String[] comments = new String[text.length];
		CodeFile file = data.getFile();
		for (int lineNum=0;lineNum<text.length;++lineNum) {
			String line = text[lineNum];
			// If no comment is found, default is that the code is the whole line and the comment is empty.
			code[lineNum] = line;
			comments[lineNum] = "";
			boolean isPreviousAWordChar = false;
			for (int j=0;j<line.length();++j) {
				char c = line.charAt(j);
				if (c==';') {
					code[lineNum] = line.substring(0,j);
					comments[lineNum] = line.substring(j);
					break;
				}
				// Skip over any string
				// NOTE: double-quote ended with double-quote; single quote ended with single quote
				else if (c=='"' || c=='\'') {
					for (++j;j<line.length() && line.charAt(j)!=c;++j) {}
				}
				// Also handle special case of string in angle brackets
				else if (c=='<') {
					for (++j;j<line.length() && line.charAt(j)!='>';++j) {}
				}
				// Check for block comment
				// NOTE: The entire content of the final line is still inside the comment.
				else if (!isPreviousAWordChar && (c|0x20)=='c') {
					final int afterComment = j+"comment".length();
					if (line.regionMatches(true,j+1,"omment",0,"omment".length()) && (afterComment>=line.length() || !StringUtil.isWordChar(line.charAt(afterComment), ASM.WORD_SPECIAL_CHARS))) {
						int k = StringUtil.getIndexAfterSpace(line,j+"comment".length());
						code[lineNum] = line.substring(0,j);
						comments[lineNum] = line.substring(j);
						// Handle case where missing the ending character by only counting the first line as a comment
						// For rest, go through all of the lines
						if (k<line.length() && line.indexOf(line.charAt(k),k+1)<0) {
							char endingChar = line.charAt(k);
							for (++lineNum;lineNum<text.length;++lineNum) {
								line = text[lineNum];
								code[lineNum] = "";
								comments[lineNum] = line;
								if (line.indexOf(endingChar)>=0) {
									break;
								}
							}
						}
						break;
					}
				}
				// Handle multi-line statements by combining them
				else if (c=='\\') {
					// TODO: Decide on a good way to handle this.
					data.getLog().add(file.getName()+"("+lineNum+"): Sorry, multi-line statements aren't supported yet.");
				}
				isPreviousAWordChar = StringUtil.isWordChar(c,ASM.WORD_SPECIAL_CHARS);
			}
		}

		UnparsedText u = new UnparsedText(code,comments,0);
		int startUnparsed = -1;
		for (int lineNum=0;lineNum<u.length();++lineNum) {
			boolean somethingFound = false;
			String line = code[lineNum];
			int afterSpace = StringUtil.getIndexAfterSpace(line);
			if (afterSpace!=line.length()) {
				if (line.regionMatches(true,afterSpace,"include",0,"include".length())) {
					int afterInclude = afterSpace+"include".length();
					int afterIncludeLib = afterInclude+"lib".length();
					// If include line
					if (line.length()>afterInclude && line.charAt(afterInclude)<=' ') {
						startUnparsed = handleUnparsedEnd(content,startUnparsed,u,lineNum);
						String filename = line.substring(afterSpace+"include".length()+1).trim();
						// TODO: Don't ignore comments on include lines.
						content.add(new Project.ExternalInclude(filename,"",file,-1));
						somethingFound = true;
					}
					// If includelib line
					else if (line.length()>afterIncludeLib && line.regionMatches(true,afterInclude,"lib",0,"lib".length()) && line.charAt(afterIncludeLib)<=' ') {
						startUnparsed = handleUnparsedEnd(content,startUnparsed,u,lineNum);
						String filename = line.substring(afterSpace+"includelib".length()+1).trim();
						if (filename.toLowerCase().endsWith(".lib")) {
							filename = filename.substring(0,filename.length()-4);
						}
						project.getLibraryIncludes().add(new Project.ExternalInclude(filename,ASMParse.instance.trimComment(comments[lineNum]),file,project.getLibraryIncludes().size()));
						somethingFound = true;
					}
				}
			}
			if (!somethingFound && startUnparsed<0) {
				startUnparsed = lineNum;
			}
		}
		if (startUnparsed>=0) {
			handleUnparsedEnd(content,startUnparsed,u,u.length());
//			content.add(ArrayUtil.subarray(text,startUnparsed,text.length));
		}

		// Check through all of the includes
		for (Iterator it = content.iterator(); it.hasNext();) {
			Object o = it.next();
			if (o instanceof Project.ExternalInclude) {
				Project.ExternalInclude include = (Project.ExternalInclude)o;
				String name = include.getName().toString();
				boolean isInc = name.toLowerCase().endsWith(".inc");
				String nameWithoutInc = (isInc) ? name.substring(0, name.length() - 4) : name;
				boolean isAlreadyIncluded = false;
				// Check for include files that correspond with included libraries and remove them if they do
				for (Project.ExternalInclude e : project.getLibraryIncludes()) {
					if (nameWithoutInc.equalsIgnoreCase(e.getName().toString())) {
						isAlreadyIncluded = true;
						break;
					}
				}
				if (!isAlreadyIncluded) {
					boolean isExternal = true;
					// Check for includes that are external and not library includes
					for (FileLoadingData d : allData) {
						if (name.equalsIgnoreCase(d.getFile().getName().toString())) {
							isExternal = false;
							break;
						}
					}
					if (isExternal) {
						include.setLocation(file,project.getImportedIncludes().size());
						project.getImportedIncludes().add(include);
					}
				}
				it.remove();
			}
		}
	}

	/**
	 * Adds an array of unparsed lines to the content if there are any such lines since the last parsed object.
	 * @param content where to add the array of unparsed lines
	 * @param startUnparsed index into text of the start of the unparsed text, or -1 if none
	 * @param text the array of lines potentially containing some unparsed lines
	 * @param lineNum the current line number
	 * @return the new value of startUnparsed (always -1)
	 */
	private int handleUnparsedEnd(ArrayList<Object> content, int startUnparsed, UnparsedText text, int lineNum) {
		if (startUnparsed>=0) {
			content.add(text.getSubrange(startUnparsed,lineNum));
		}
		return -1;
	}

	/**
	 * This scan checks for constants and structures, but does not interpret the content.
	 * @param data the data to be parsed
	 * @param allData list of all files being parsed
	 * @param project the current project, whose global {@link Scope} has notification turned off during load
	 */
	public void constantScan(FileLoadingData data,ArrayList<FileLoadingData> allData, Project project) {
		final Scope globalScope = project.getScope();
		final ArrayList<Object> content = new ArrayList<Object>();
		for (Object o : data.getContent()) {
			if (!(o instanceof UnparsedText)) {
				content.add(o);
			}
			else {	// o instanceof UnparsedText
				UnparsedText u = (UnparsedText)o;
				int startUnparsed = -1;
				for (int lineNum = 0; lineNum < u.length(); ++lineNum) {
					boolean somethingFound = false;
					String line = u.code[lineNum];
					int afterSpace = StringUtil.getIndexAfterSpace(line);
					if (afterSpace!=line.length()) {
						String firstWord = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS, ASM.FIRST_WORD_SPECIAL_CHARS);
						String firstLower = firstWord.toLowerCase();
						String secondWord;
						if (firstLower.equals("struct") || firstLower.equals("equ") || firstLower.equals("textequ") || (line.length() >= afterSpace + 1 && line.charAt(afterSpace)=='=')) {
							secondWord = firstWord;
							firstWord = "";
						}
						else {
							// Find the second word on the line and check whether it is "struct" or "equ"
							afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + firstWord.length());
							secondWord = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
						}
						boolean isConstant = false;
						boolean isPositionIndep = false;
						boolean isRedefinable = false;
						String secondLower = secondWord.toLowerCase();
						if (secondLower.equals("struct")) {
							startUnparsed = handleUnparsedEnd(content, startUnparsed, u, lineNum);
							Structure structure = new Structure(firstWord, ASMParse.instance.trimComment(u.comments[lineNum]));
							String patternString = "\\s*" + (firstWord.replace("?", "\\?")) + "\\s+[Ee][Nn][Dd][Ss](\\s.*|;.*)?";
							Pattern pattern = Pattern.compile(patternString);
							int endLine = lineNum + 1;
							while (endLine < u.length() && !pattern.matcher(u.code[endLine]).matches()) {
								++endLine;
							}
							globalScope.add(structure);
							content.add(structure);
							data.addToStructureList(structure,u.getSubrange(lineNum+1, endLine));
							lineNum = endLine;	// Continue after the end line, which may be just after the end of text
							somethingFound = true;
						}
						// If position-dependent, non-redefinable constant
						else if (secondLower.equals("equ")) {
							isConstant = true;
						}
						// If position-independent, non-redefinable constant
						else if (secondLower.equals("textequ")) {
							isConstant = true;
							isPositionIndep = true;
						}
						// If position-dependent, redefinable constant
						else if (line.length() >= afterSpace + 1 && line.charAt(afterSpace)=='=') {
							isConstant = true;
							isRedefinable = true;
							secondWord = "=";
						}
						// Handle all 3 types of constants together
						if (isConstant) {
							startUnparsed = handleUnparsedEnd(content, startUnparsed, u, lineNum);
							String comment = ASMParse.instance.trimComment(u.comments[lineNum]);
							String value = line.substring(afterSpace + secondWord.length()).trim();
							// If the value was just generated by the enumeration for compatibility, clear it.
							if (comment.equals(ASM.ENUMERATION_VALUE_MARKER)) {
								comment = "";
								value = "";
							}
							Constant constant = new Constant(firstWord,comment,value,isPositionIndep,isRedefinable);
							content.add(constant);
							globalScope.add(constant);
							somethingFound = true;
						}
					}
					if (!somethingFound && startUnparsed < 0) {
						startUnparsed = lineNum;
					}
				}
				if (startUnparsed >= 0) {
					content.add(u.getSubrange(startUnparsed,u.length()));
				}
			}
		}
		data.setContent(content);
	}

	/**
	 * This scan checks for functions, global variables, and segments, but does not interpret the content.
	 * @param data the data to be parsed
	 * @param allData list of all files being parsed
	 * @param project the current project, whose global {@link Scope} has notification turned off during load
	 */
	public void globalScan(FileLoadingData data,ArrayList<FileLoadingData> allData, Project project) {
		final CodeFile file = data.getFile();
		final Scope globalScope = project.getScope();
		final ArrayList<Object> content = new ArrayList<Object>();
		Segment segment = null;
		boolean isSegmentDeclarationDefault = true;
		int segmentStart = 0;
		boolean isSegmentNeeded = false;
		for (Object o : data.getContent()) {
			if (!(o instanceof UnparsedText)) {
				content.add(o);
			}
			else {	// o instanceof UnparsedText
				UnparsedText u = (UnparsedText)o;
				int startUnparsed = -1;
				for (int lineNum = 0; lineNum < u.length(); ++lineNum) {
					boolean somethingFound = false;
					String line = u.code[lineNum];
					int afterSpace = StringUtil.getIndexAfterSpace(line);
					if (afterSpace!=line.length()) {
						String firstWord = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS, ASM.FIRST_WORD_SPECIAL_CHARS);
						String firstLowercase = firstWord.toLowerCase();
						GlobalVariable variable = null;
						Keyword defaultSegment = defaultSegmentKeywords.get(firstWord);
						if (defaultSegment!=null) {
							startUnparsed = handleUnparsedEnd(content, startUnparsed, u, lineNum);
							Segment newSegment;
							if (defaultSegment==ASM.CODE_SEGMENT) {
								newSegment = project.getDefaultSegment(Segment.CODE);
							}
							else if (defaultSegment==ASM.INIT_DATA_SEGMENT) {
								newSegment = project.getDefaultSegment(Segment.INIT_DATA);
							}
							else if (defaultSegment==ASM.UNINIT_DATA_SEGMENT) {
								newSegment = project.getDefaultSegment(Segment.UNINIT_DATA);
							}
							else { // defaultSegment==ASM.READ_ONLY_DATA_SEGMENT
								newSegment = project.getDefaultSegment(Segment.READ_ONLY_DATA);
							}
							if (segment!=null && !isSegmentDeclarationDefault) {
								data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Segment "+newSegment.getName()+" is starting before "+segment.getName()+" has ended.");
							}
							if (segment!=newSegment) {
								if (segment!=null && isSegmentNeeded) {
									// Add it once at the beginning of the segment and again marking the end
									content.add(segmentStart, segment);
									content.add(segment);
								}
								segment = newSegment;
								isSegmentDeclarationDefault = true;
								segmentStart = content.size();
								int type = segment.getType();
								isSegmentNeeded = (type!=Segment.CODE) && (type!=Segment.INIT_DATA);
							}
							somethingFound = true;
						}
						// Check for anonymous variable with type that is in the language scope
						else if (firstLowercase.equals("ptr") || dataDeclarationKeywords.get(firstLowercase)!=null) {
							// NOTE: Don't need to call handleUnparsedEnd or set somethingFound here, since it's done below.
							String type = firstWord;
							String typeWord = firstWord;
							while (typeWord.equalsIgnoreCase("ptr")) {
								type += " ";
								afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + typeWord.length());
								typeWord = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
								type += typeWord;
							}
							variable = new GlobalVariable("", ASMParse.instance.trimComment(u.comments[lineNum]));
							variable.getPieces().add(new GlobalVariable.Piece(type, line.substring(afterSpace + typeWord.length()).trim(),variable,0));
						}
						else if (firstWord.length()>0) {
							// Check for anonymous variable with type that is in the global scope
							ArrayList<Symbol> symbols = globalScope.get(firstWord);
							Structure type = null;
							if (symbols!=null) {
								for (Symbol symbol : symbols) {
									if (symbol instanceof Structure) {
										type = (Structure)symbol;
										break;
									}
								}
							}
							if (type!=null) {
								// NOTE: Don't need to call handleUnparsedEnd or set somethingFound here, since it's done below.
								variable = new GlobalVariable("", ASMParse.instance.trimComment(u.comments[lineNum]));
								variable.getPieces().add(new GlobalVariable.Piece(firstWord, line.substring(afterSpace + firstWord.length()).trim(),variable,0));
							}
							else {
								String secondWord;
								// Handle anonymous functions (not valid code, but in case user didn't enter function name)
								if (firstLowercase.equals("proc") || firstLowercase.equals("proto") || firstLowercase.equals("macro")) {
									secondWord = firstWord;
									firstLowercase = "";
									firstWord = "";
								}
								else {
									// Find the second word on the line and check whether it is "proc", "segment", or a DataType
									afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + firstWord.length());
									secondWord = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
								}
								if (secondWord.length()>0) {
									String secondLowercase = secondWord.toLowerCase();
									// If a function or prototype
									// TODO: Handle "extern function:PROC" somehow.
									if (secondLowercase.equals("proc") || secondLowercase.equals("proto") || secondLowercase.equals("macro")) {
										startUnparsed = handleUnparsedEnd(content, startUnparsed, u, lineNum);
										Function function = new Function(firstWord, ASMParse.instance.trimComment(u.comments[lineNum]), ASM.language);
										afterSpace = StringUtil.getIndexAfterSpace(line,afterSpace+secondWord.length());
										String parameterName = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
										// Check for calling convention specified in < > brackets
										if (parameterName.length()==0 && afterSpace < line.length() && line.charAt(afterSpace)=='<') {
											int endIndex = line.indexOf('>');
											if (endIndex==-1) {
												data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): \"<\" was found in declaring function " + function.getName() + " without a closing \">\", so the opening character is being ignored.");
												++afterSpace;
											}
											else {
												String[] callingConventions = Function.CALLING_CONVENTION_NAMES;
												String text = line.substring(afterSpace, endIndex);
												int index = ArrayUtil.indexOf(callingConventions, text);
												if (index >= 0) {
													function.getCallingConvention().setValue(callingConventions[index]);
												}
												afterSpace += 2 + text.length();
											}
											afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace);
											parameterName = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
										}
										// Check for calling convention outside of < > brackets (more common, but more risk of confusion)
										else if (parameterName.equalsIgnoreCase(Function.FAR_TEXT)) {
											int beforeSpace = afterSpace + Function.FAR_TEXT.length();
											afterSpace = StringUtil.getIndexAfterSpace(line,beforeSpace);
											// Must be end of line or followed by something other than colon (including whitespace)
											if (afterSpace==line.length() || line.charAt(beforeSpace)==':') {
												function.getCallingConvention().setValue(Function.FAR_TEXT);
											}
											parameterName = StringUtil.getWord(line,afterSpace,ASM.WORD_SPECIAL_CHARS);
										}
										else if (parameterName.equalsIgnoreCase(Function.C_TEXT)) {
											int beforeSpace = afterSpace + Function.FAR_TEXT.length();
											afterSpace = StringUtil.getIndexAfterSpace(line,beforeSpace);
											// Must be end of line or followed by something other than colon (including whitespace)
											if (afterSpace==line.length() || line.charAt(beforeSpace)==':') {
												function.getCallingConvention().setValue(Function.C_TEXT);
											}
											parameterName = StringUtil.getWord(line,afterSpace,ASM.WORD_SPECIAL_CHARS);
										}
										// Look for parameters, including unnamed parameters
										while (parameterName.length()>0 || (afterSpace<line.length() && line.charAt(afterSpace)==':')) {
											int index = StringUtil.getIndexAfterSpace(line, afterSpace + parameterName.length());
											String typeString = "";
											int endOfType = index;
											// Skip over colon and space if parameter has a type (note that macro parameters don't need types)
											if (index<line.length() && line.charAt(index)==':') {
												index = StringUtil.getIndexAfterSpace(line, index + 1);
												// The type is the text from here to comma or end of line
												endOfType = line.indexOf(',',index);
												if (endOfType < 0) {
													endOfType = line.length();
												}
												typeString = line.substring(index, endOfType).trim();
											}
											function.addParameter(new LocalVariable(parameterName, typeString,"",function,CodeFileChunk.LOCATION_PARAMETERS,function.getParameters().size()));
											afterSpace = StringUtil.getIndexAfterSpace(line, endOfType + 1);
											parameterName = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
										}
										// Handling not applicable to prototypes
										if (!secondLowercase.equals("proto")) {
											IntList blankLines = new IntList();
											// Look for local variables
											for (++lineNum; lineNum < u.length(); ++lineNum) {
												line = u.code[lineNum];
												afterSpace = StringUtil.getIndexAfterSpace(line);
												String word = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS, ASM.FIRST_WORD_SPECIAL_CHARS);
												if (word.equalsIgnoreCase("local")) {
													afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + "local".length());
													int colonIndex = line.indexOf(':', afterSpace);
													if (colonIndex >= 0) {
														// TODO: Handle local arrays.  They have the format "LOCAL name[length]:type".
														// TODO: Handle multiple local variables on the same line, separated by commas.
														String name = line.substring(afterSpace, colonIndex).trim();
														String typeString = line.substring(colonIndex + 1);
														LocalVariable var = new LocalVariable(name, typeString, ASMParse.instance.trimComment(u.comments[lineNum]),function,CodeFileChunk.LOCATION_LOCAL_VARS,function.getLocalVariables().size());
														// Add variables at the beginning so that their order is reversed.
														// Their order in memory is the opposite of their declaration order.
														function.addLocalVariable(var, 0);
													}
													else {
														data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Ignoring declaration that symbol " + line.substring(afterSpace).trim() + " in function " + function.getName() + " is local, since it is inherently treated as local.");
													}
												}
												// TODO: Find a way to figure out whether comments are for the function or for local variables so that they can be handled appropriately
												else if (afterSpace < line.length() || u.comments[lineNum].length() > 0) {
													break;
												}
												else {
													// Keep track of blank lines so that they can be saved
													blankLines.add(lineNum);
												}
											}
											lineNum -= blankLines.size();
											int endLine = lineNum;
											for (int i=blankLines.size()-1;i>=0;--i) {
												u.code[endLine+i] = u.code[blankLines.get(i)];
												u.comments[endLine+i] = "";
											}
											// Find end of function
											String patternString;
											if (secondLowercase.equals("proc")) {
												patternString = "^\\s*" + (firstWord.replace("?", "\\?")) + "\\s+[Ee][Nn][Dd][Pp]";
											}
											else {
												patternString = "^\\s*[Ee][Nn][Dd][Mm]";
												function.getCallingConvention().setValue(Function.INLINE_TEXT);
											}
											Pattern pattern = Pattern.compile(patternString);
											while (endLine < u.length() && !pattern.matcher(u.code[endLine]).matches()) {
												++endLine;
											}
											globalScope.add(function);
											content.add(function);
											// NOTE: The item following the Function is an array of lines inside it.
											content.add(u.getSubrange(lineNum, endLine));
											lineNum = endLine;	// Continue after the end line, which may be just after the end of text
										}
										// Special handling for prototypes
										else {
											Prototypes prototype = new Prototypes(function);
											content.add(prototype);
											data.getPrototypeList().add(prototype);
										}
										somethingFound = true;
									}
									// If a segment
									else if (secondLowercase.equals("segment")) {
										startUnparsed = handleUnparsedEnd(content, startUnparsed, u, lineNum);
										Segment newSegment = project.getSegment(firstWord);
										if (newSegment==null) {
											newSegment = new Segment(firstWord);
											project.addSegment(newSegment);
										}
										// Check for segment information in case its not elsewhere
										afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + secondWord.length());
										boolean hasRead = false;
										boolean hasWrite = false;
										boolean hasExecute = false;
										while (afterSpace < line.length()) {
											String word = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
											String wordLow = word.toLowerCase();
											if (wordLow.equals("use16")) {
												newSegment.getnBits().set(16);
											}
											else if (wordLow.equals("use32")) {
												newSegment.getnBits().set(32);
											}
											else if (wordLow.equals("use64") || wordLow.equals("flat")) {
												newSegment.getnBits().set(64);
											}
											else if (wordLow.equals("byte")) {
												newSegment.getAlignment().set(1);
											}
											else if (wordLow.equals("word")) {
												newSegment.getAlignment().set(2);
											}
											else if (wordLow.equals("dword")) {
												newSegment.getAlignment().set(4);
											}
											else if (wordLow.equals("para")) {
												newSegment.getAlignment().set(16);
											}
											else if (wordLow.equals("page")) {
												newSegment.getAlignment().set(256);
											}
											else if (wordLow.equals("align")) {
												afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + wordLow.length());
												if (afterSpace < line.length() && line.charAt(afterSpace)=='(') {
													afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + 1);
												}
												if (afterSpace < line.length()) {
													String alignmentText = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
													try {
														int alignment = Integer.parseInt(alignmentText);
														// Ensure that it's a power of two (one less than itself has no set bits in common)
														if ((alignment & (alignment - 1))!=0 && alignment!=0) {
															throw new NumberFormatException();
														}
														newSegment.getAlignment().set(alignment);
													}
													catch (NumberFormatException e) {
														data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Invalid alignment \"" + alignmentText + "\" found on segment \"" + newSegment.getName() + "\".");
													}
													afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + alignmentText.length());
													if (afterSpace < line.length() && line.charAt(afterSpace)==')') {
														afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + 1);
													}
												}
											}
											else if (wordLow.equals("at")) {
												afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + wordLow.length());
												String addressText = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
												try {
													char lastChar = (addressText.length() > 0) ? addressText.charAt(addressText.length() - 1) : 0;
													// Only bother to support hexademical and decimal for the address, since that's all anyone would use
													long address = Long.parseLong(addressText, ((lastChar | 0x20)=='h') ? 16 : 10);
													newSegment.getAddress().set(address);
												}
												catch (NumberFormatException e) {
													data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Invalid address \"" + addressText + "\" found on segment \"" + newSegment.getName() + "\".");
												}
												afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + addressText.length());
											}
											else if (wordLow.equals("read")) {
												hasRead = true;
											}
											else if (wordLow.equals("write")) {
												hasWrite = true;
											}
											else if (wordLow.equals("execute")) {
												hasExecute = true;
											}
											else if (wordLow.equals("shared")) {
												newSegment.setShared(true);
											}
											else if (wordLow.equals("nopage")) {
												newSegment.setNoPage(true);
											}
											else if (wordLow.equals("nocache")) {
												newSegment.setNoCache(true);
											}
											else if (wordLow.equals("discard")) {
												newSegment.setDiscardable(true);
											}
											else if (wordLow.equals("readonly")) {
												hasRead = true;
												hasWrite = false;
												hasExecute = false;
											}
											afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + secondWord.length());
										}
										if (hasRead || hasWrite || hasExecute) {
											newSegment.setReadable(hasRead);
											newSegment.setWritable(hasWrite);
											newSegment.setExecutable(hasExecute);
										}
										if (segment!=null && !isSegmentDeclarationDefault) {
											data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Segment " + firstWord + " is starting before "+segment.getName()+" has ended.");
										}
										// Handle a change of segment
										if (newSegment!=segment) {
											if (segment!=null && isSegmentNeeded) {
												// Add it once at the beginning of the segment and again marking the end
												content.add(segmentStart, segment);
												content.add(segment);
											}
											segment = newSegment;
											isSegmentDeclarationDefault = false;
											segmentStart = content.size();
											isSegmentNeeded = true;
										}
										somethingFound = true;
									}
									// If end of explicit segment
									else if (secondLowercase.equals("ends")) {
										if (segment==null || isSegmentDeclarationDefault) {
											data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Segment " + firstWord + " has an explicit end, but no explicit beginning, so it is being ignored.");
										}
										else {
											if (!segment.getName().toString().equals(firstWord)) {
												data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Segment " + segment.getName() + " is being ended as segment " + firstWord + " instead, so the ending is being treated as the ending of segment " + segment.getName() + ".");
											}
											// Add it once at the beginning of the segment and again marking the end
											content.add(segmentStart, segment);
											content.add(segment);
											segment = null;
											segmentStart = content.size();
											isSegmentNeeded = false;
										}
									}
									else if (allKeywords.get(firstLowercase)==null) {
										// Check for named variable with a type in language scope
										if (secondLowercase.equals("ptr") || dataDeclarationKeywords.get(secondLowercase)!=null) {
											// NOTE: Don't need to call handleUnparsedEnd or set somethingFound here, since it's done below.
											variable = new GlobalVariable(firstWord, ASMParse.instance.trimComment(u.comments[lineNum]));
											String typeString = secondWord;
											String typeWord = secondWord;
											while (typeWord.equalsIgnoreCase("ptr")) {
												typeString += " ";
												afterSpace = StringUtil.getIndexAfterSpace(line, afterSpace + typeWord.length());
												typeWord = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
												typeString += typeWord;
											}
											variable.getPieces().add(new GlobalVariable.Piece(typeString, line.substring(afterSpace + typeWord.length()).trim(),variable,variable.getPieces().size()));
										}
										// Check for named variable with structure type
										else {
											symbols = globalScope.get(secondWord);
											type = null;
											if (symbols!=null) {
												for (Symbol symbol : symbols) {
													if (symbol instanceof Structure) {
														type = (Structure)symbol;
														break;
													}
												}
											}
											if (type!=null) {
												// NOTE: Don't need to call handleUnparsedEnd or set somethingFound here, since it's done below.
												variable = new GlobalVariable(firstWord, ASMParse.instance.trimComment(u.comments[lineNum]));
												variable.getPieces().add(new GlobalVariable.Piece(secondWord, line.substring(afterSpace + secondWord.length()).trim(),variable,variable.getPieces().size()));
											}
										}
									}
								}
							}
						}
						if (variable!=null) {
							startUnparsed = handleUnparsedEnd(content, startUnparsed, u, lineNum);
							// If the previous item was a variable and this one has no name, combine them
							// NOTE: To have an anonymous variable be separate from a named one before it, there should be an empty line between.
							Object obj = (!content.isEmpty()) ? content.get(content.size() - 1) : "";		// A String is definitely not a GlobalVariable, but null might be considered so
							// NOTE: To put back in the check for same types, use "((GlobalVariable)obj).getDefaultType().equalsIgnoreCase(variable.getDefaultType())"
							if (variable.getName().length()==0 && obj instanceof GlobalVariable) {
								variable.getPieces().get(0).setLocation(((GlobalVariable)obj),((GlobalVariable)obj).getPieces().size());
								((GlobalVariable)obj).getPieces().add(variable.getPieces().get(0));
							}
							else {
								globalScope.add(variable);	// Add to globalScope even if anonymous in case it gets a name later
								content.add(variable);
							}
							somethingFound = true;
						}
					}
					if (!somethingFound && startUnparsed < 0) {
						startUnparsed = lineNum;
					}
				}
				if (startUnparsed >= 0) {
					content.add(u.getSubrange(startUnparsed, u.length()));
				}
			}
		}
		if (segment!=null && isSegmentNeeded) {
			// Add it once at the beginning of the segment and again marking the end
			content.add(segmentStart, segment);
			content.add(segment);
		}
		data.setContent(content);
	}

	/**
	 * This scan checks whether prototypes are redundant, and interprets the content of structures.
	 * @param data the data to be parsed
	 * @param allData list of all files being parsed
	 * @param project the current project, whose global {@link Scope} has notification turned off during load
	 */
	public void structureScan(FileLoadingData data, ArrayList<FileLoadingData> allData, Project project) {
		final CodeFile file = data.getFile();
		final Scope globalScope = project.getScope();
		final ArrayList<Prototypes> prototypes = data.getPrototypeList();
		final ArrayList content = data.getContent();
		for (Prototypes prototype : prototypes) {
			Function function = prototype.getFunctions().get(0);
			ArrayList<Symbol> symbols = globalScope.get(function.getName().toString());
			Function f2 = null;
			if (symbols!=null) {
				for (Symbol symbol : symbols) {
					if (symbol instanceof Function) {
						// TODO: Check parameter types in order to support function overloading.
						f2 = (Function)symbol;
						prototype.getFunctions().set(0,f2);
						content.remove(prototype);
						break;
					}
				}
			}
			if (f2==null) {
				globalScope.add(function);
			}
		}

		final ArrayList<Pair<Structure,UnparsedText>> structures = data.getStructuresToParse();
		for (Pair<Structure,UnparsedText> pair : structures) {
			Structure structure = pair.a;
			UnparsedText u = pair.b;
			int prevCommentPosition = -1;
			String nextMemberComment = "";
			StructureMember prevMember = null;
			for (int lineNum=0;lineNum<u.length();++lineNum) {
				boolean memberFound = false;
				String line = u.code[lineNum];
				String comment = u.comments[lineNum];
				String trimmedComment = ASMParse.instance.trimComment(comment);
				int afterSpace = StringUtil.getIndexAfterSpace(line);
				if (afterSpace!=line.length()) {
					String firstWord = StringUtil.getWord(line,afterSpace, ASM.WORD_SPECIAL_CHARS, ASM.FIRST_WORD_SPECIAL_CHARS);
					String firstLowercase = firstWord.toLowerCase();
					// Check for anonymous variable with type that is in the language scope
					if (firstLowercase.equals("ptr") || dataDeclarationKeywords.get(firstLowercase)!=null) {
						String type = firstWord;
						String typeWord = firstWord;
						while (typeWord.equalsIgnoreCase("ptr")) {
							type += " ";
							afterSpace = StringUtil.getIndexAfterSpace(line,afterSpace+typeWord.length());
							typeWord = StringUtil.getWord(line,afterSpace,ASM.WORD_SPECIAL_CHARS);
							type += typeWord;
						}

						prevMember = new StructureMember("",type,line.substring(afterSpace+typeWord.length()).trim(),nextMemberComment+((nextMemberComment.length()==0 || trimmedComment.length()==0)?"":" ")+trimmedComment,structure,structure.getMembers().size());
						structure.addMember(prevMember);
						memberFound = true;
					}
					else if (firstWord.length()>0) {
						// Check for anonymous variable with type that is in the global scope
						ArrayList<Symbol> symbols = globalScope.get(firstWord);
						Structure type = null;
						if (symbols!=null) {
							for(Symbol symbol : symbols) {
								if (symbol instanceof Structure) {
									type = (Structure)symbol;
									break;
								}
							}
						}
						if (type!=null) {
							prevMember = new StructureMember("",firstWord,line.substring(afterSpace+firstWord.length()).trim(),nextMemberComment+((nextMemberComment.length()==0 || trimmedComment.length()==0)?"":" ")+trimmedComment,structure,structure.getMembers().size());
							structure.addMember(prevMember);
							memberFound = true;
						}
						else if (allKeywords.get(firstLowercase)==null) {
							// Find the second word on the line and check whether it is a DataType
							afterSpace = StringUtil.getIndexAfterSpace(line,afterSpace+firstWord.length());
							String secondWord = StringUtil.getWord(line,afterSpace, ASM.WORD_SPECIAL_CHARS);
							String secondLowercase = secondWord.toLowerCase();
							// Check for named variable with a type in language scope
							if (secondLowercase.equals("ptr") || dataDeclarationKeywords.get(secondLowercase)!=null) {
								String typeString = secondWord;
								String typeWord = secondWord;
								while (typeWord.equalsIgnoreCase("ptr")) {
									typeString += " ";
									afterSpace = StringUtil.getIndexAfterSpace(line,afterSpace+typeWord.length());
									typeWord = StringUtil.getWord(line,afterSpace, ASM.WORD_SPECIAL_CHARS);
									typeString += typeWord;
								}
								prevMember = new StructureMember(firstWord,typeString,line.substring(afterSpace+typeWord.length()).trim(),nextMemberComment+((nextMemberComment.length()==0 || trimmedComment.length()==0)?"":" ")+trimmedComment,structure,structure.getMembers().size());
								structure.addMember(prevMember);
								memberFound = true;
							}
							// Check for named variable with structure type
							else if (secondWord.length()>0) {
								symbols = globalScope.get(secondWord);
								type = null;
								if (symbols!=null) {
									for(Symbol symbol : symbols) {
										if (symbol instanceof Structure) {
											type = (Structure)symbol;
											break;
										}
									}
								}
								if (type!=null) {
									prevMember = new StructureMember(firstWord,secondWord,line.substring(afterSpace+secondWord.length()).trim(),nextMemberComment+((nextMemberComment.length()==0 || trimmedComment.length()==0)?"":" ")+trimmedComment,structure,structure.getMembers().size());
									structure.addMember(prevMember);
									memberFound = true;
								}
								else {
									data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Member of structure "+structure+" named "+firstWord+" with unknown type "+secondWord+"; adding it anyway.");
									prevMember = new StructureMember(firstWord,secondWord,line.substring(afterSpace+secondWord.length()).trim(),nextMemberComment+((nextMemberComment.length()==0 || trimmedComment.length()==0)?"":" ")+trimmedComment,structure,structure.getMembers().size());
									structure.addMember(prevMember);
									memberFound = true;
								}
							}
						}
						// Preprocessor lines aren't handled yet, so log it
						else if (preprocessorKeywords.get(firstLowercase)!=null) {
							// TODO: Figure out a way to handle preprocessor lines like conditional inclusion and error in structures.
							data.getLog().add(file.getName()+"("+(u.startLineNumber+lineNum)+"): Structure "+structure.getName()+" contains a preprocessor line, which isn't supported at the moment.  Sorry.");
						}
						// Unknown lines just plain aren't handled
						else {
							data.getLog().add(file.getName()+"("+(u.startLineNumber+lineNum)+"): Structure "+structure.getName()+" contains a line that isn't supported at the moment.  Sorry.");
						}
					}

					if (memberFound) {
						prevCommentPosition = (u.comments[lineNum].length()>0) ? StringUtil.getUntabbedLength(line) : -1;
						nextMemberComment = "";
					}
				}
				// Handle comment lines
				else if (comment.length()>0) {
					// Put comment with the previous member if aligned to the same position as comment on it
					if (prevCommentPosition==StringUtil.getUntabbedLength(line)) {
						prevMember.getDescription().append(" "+trimmedComment);
					}
					// Otherwise, put it with the next member
					else {
						nextMemberComment = ((nextMemberComment.length()==0)?"":" ")+trimmedComment;
						prevCommentPosition = -1;
					}
				}
				// Ignore empty lines
			}
		}
	}

	/**
	 * This scan interprets the content of functions and gathers together prototypes and constants.
	 * @param data the data to be parsed
	 * @param allData list of all files being parsed
	 * @param project the current project, whose global {@link Scope} has notification turned off during load
	 */
	public void contentScan(FileLoadingData data, ArrayList<FileLoadingData> allData, Project project) {
		final CodeFile file = data.getFile();
		final ArrayList<Object> content = new ArrayList<Object>();
		Prototypes currentPrototype = null;
		Constants currentConstants = null;
		for (Iterator it=data.getContent().iterator();it.hasNext();) {
			Object o = it.next();

			if (currentPrototype!=null && !(o instanceof Prototypes)) {
				content.add(currentPrototype);
				currentPrototype = null;
			}
			else if (currentConstants!=null && !(o instanceof Constant)) {
				content.add(currentConstants);
				currentConstants = null;
			}

			if (o instanceof UnparsedText) {
				UnparsedText u = (UnparsedText)o;
				for (int lineNum=0;lineNum<u.length();++lineNum) {
					String line = u.code[lineNum];
					int afterSpace = StringUtil.getIndexAfterSpace(line);
					String wordLowercase = StringUtil.getWord(line,afterSpace,ASM.WORD_SPECIAL_CHARS,ASM.FIRST_WORD_SPECIAL_CHARS);
					boolean isPreprocessorLine = false;
					if (wordLowercase.equals("end")) {
						afterSpace = StringUtil.getIndexAfterSpace(line,afterSpace+"end".length());
						String codeStart = StringUtil.getWord(line,afterSpace,ASM.WORD_SPECIAL_CHARS);
						if (codeStart!=null && codeStart.length()>0) {
							// TODO: Find the function associated with the code start and track the reference to it.
							project.getEntryPointLine().replaceAll(codeStart);
						}
						isPreprocessorLine = true;
					}
					else if (preprocessorKeywords.containsKey(wordLowercase)) {
						// FIXME: Handle preprocessor lines!!!
						isPreprocessorLine = true;
					}
					// FIXME: Remove lines found to be preprocessor lines, only adding the rest!!!
				}
				content.add(o);
			}
			else if (o instanceof Prototypes) {
				Prototypes prototype = (Prototypes)o;
				if (currentPrototype==null) {
					currentPrototype = prototype;
				}
				else {
					currentPrototype.getFunctions().add(prototype.getFunctions().get(0));
				}
			}
			else if (o instanceof Constant) {
				Constant constant = (Constant)o;
				if (currentConstants==null) {
					currentConstants = new Constants("","");
				}
				currentConstants.add(constant);
				constant.setLocation(currentConstants,currentConstants.getMembers().size()-1);
			}
			else if (o instanceof Function) {
				Function function = (Function)o;
				UnparsedText u = (UnparsedText)it.next();
				MultiLineList code = function.getCode();
				// Remove the empty line inserted initially
				Line dummyLine = code.remove(0);
				// Look for more local variables
				for (int lineNum=0;lineNum<u.length();++lineNum) {
					String line = u.code[lineNum];
					String comment = u.comments[lineNum];
					int afterSpace = StringUtil.getIndexAfterSpace(line);
					String word = StringUtil.getWord(line,afterSpace,ASM.WORD_SPECIAL_CHARS,ASM.FIRST_WORD_SPECIAL_CHARS);
					if (word.equalsIgnoreCase("local")) {
						afterSpace = StringUtil.getIndexAfterSpace(line,afterSpace+"local".length());
						int colonIndex = line.indexOf(':',afterSpace);
						if (colonIndex>=0) {
							// TODO: Handle local arrays.  They have the format "LOCAL name[length]:type".
							// TODO: Handle multiple local variables on the same line, separated by commas.
							String name = line.substring(afterSpace,colonIndex).trim();
							String typeString = line.substring(colonIndex+1);
							LocalVariable var = new LocalVariable(name,typeString,ASMParse.instance.trimComment(comment),function,CodeFileChunk.LOCATION_PARAMETERS,function.getLocalVariables().size());
							// Add variables at the beginning so that their order is reversed.
							// Their order in memory is the opposite of their declaration order.
							function.addLocalVariable(var,0);
						}
						else {
							data.getLog().add(file.getName()+"("+(u.startLineNumber+lineNum)+"): Ignoring declaration that symbol "+line.substring(afterSpace).trim()+" in function "+function.getName()+" is local, since it is inherently treated as local.");
						}
					}
					else {
						ASMLine aLine = ASM.language.createCodeLine(code,code.size(),(line+comment).toCharArray());
						code.add(aLine);
						// Find line labels and add them to the local scope
						if (aLine.hasLabel()) {
							function.getScope().add(aLine.getLabel());
						}
					}
				}
				// If there was no code in the function, reinsert the empty line so that there's at least somewhere to click.
				if (code.size()==0) {
					code.add(dummyLine);
				}
				content.add(function);
			}
			else {
				content.add(o);
			}
		}
		if (currentPrototype!=null) {
			content.add(currentPrototype);
		}
		else if (currentConstants!=null) {
			content.add(currentConstants);
		}
		data.setContent(content);
	}

	/**
	 * This scan checks for any and all metadata (not just in doc comments) for files and global symbols,
	 * and also finds sections, groupings of constants/enumerations.  It does not put the CodeFile together
	 * or enable notification in the global {@link Scope}.  The beginning / end of segments is still marked
	 * by the {@link Segment}s themselves.
	 * @param data the data to be parsed
	 * @param allData list of all files being parsed
	 * @param project the current project, whose global {@link Scope} has notification turned off during load
	 */
	public void docScan(FileLoadingData data, ArrayList<FileLoadingData> allData, Project project) {
		final CodeFile file = data.getFile();
		final ArrayList<Object> content = new ArrayList<Object>();
		int alignment = CodeFileChunk.DEFAULT_ALIGNMENT_MARKER;
		String offset = null;
		String[] docComment = null;
		for (Object o : data.getContent()) {
			if (!(o instanceof UnparsedText)) {
				content.add(o);
				if (o instanceof Function) {
					((Function)o).getAlignment().set(alignment);
					// Alignment doesn't apply after a function it aligns
					alignment = CodeFileChunk.DEFAULT_ALIGNMENT_MARKER;
				}
				else if (o instanceof GlobalVariable) {
					((GlobalVariable)o).getAlignment().set(alignment);
					// Alignment doesn't apply after a variable it aligns
					alignment = CodeFileChunk.DEFAULT_ALIGNMENT_MARKER;
				}
				if (offset!=null) {
					if (o instanceof Function) {
						((Function)o).getOffset().replaceAll(offset);
					}
					else if (o instanceof GlobalVariable) {
						((GlobalVariable)o).getOffset().replaceAll(offset);
					}
					offset = null;
				}
				if (docComment!=null && (o instanceof CodeFileChunk)) {
					ASMDoc.instance.addDocumentation((CodeFileChunk)o,docComment);
					docComment = null;
				}
			}
			else {	// o instanceof UnparsedText
				UnparsedText u = (UnparsedText)o;
				int startUnparsed = -1;
				for (int lineNum = 0; lineNum < u.length(); ++lineNum) {
					boolean somethingFound = false;
					String line = u.code[lineNum];
					String comment = u.comments[lineNum];
					int afterSpace = StringUtil.getIndexAfterSpace(line);
					if (afterSpace!=line.length()) {
						String firstWord = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS, ASM.FIRST_WORD_SPECIAL_CHARS);
						String firstLowercase = firstWord.toLowerCase();
						if (firstLowercase.equals("align")) {
							afterSpace = StringUtil.getIndexAfterSpace(line,afterSpace+firstLowercase.length());
							if (afterSpace < line.length()) {
								String alignmentText = StringUtil.getWord(line, afterSpace, ASM.WORD_SPECIAL_CHARS);
								try {
									alignment = Integer.parseInt(alignmentText);
									// Ensure that it's a power of two (one less than itself has no set bits in common)
									if ((alignment & (alignment - 1))!=0 && alignment!=0) {
										throw new NumberFormatException();
									}
									if (offset!=null) {
										final int alignM1 = alignment-1;
										String alignM1Hex = Integer.toString(alignM1,16)+((alignM1>9)?"h":"");
										if (alignM1Hex.charAt(0)>'9') {
											alignM1Hex = "0"+alignM1Hex;
										}
										// Make offset like: ((offset)+0Fh) AND NOT(0Fh) to align in lieu of having offset and alignment
										offset = "(("+offset+")+"+alignM1Hex+") AND NOT("+alignM1Hex+")";
										alignment = CodeFileChunk.DEFAULT_ALIGNMENT_MARKER;
									}
								}
								catch (NumberFormatException e) {
									data.getLog().add(file.getName() + "(" + (u.startLineNumber + lineNum) + "): Invalid alignment \"" + alignmentText + "\" found.");
								}
							}
						}
						else if (firstLowercase.equals("org")) {
							afterSpace = StringUtil.getIndexAfterSpace(line,afterSpace+firstLowercase.length());
							offset = line.substring(afterSpace).trim();
							// Set the alignment back, since we've got a specific address now
							alignment = CodeFileChunk.DEFAULT_ALIGNMENT_MARKER;
						}
					}
					else if (comment.length()>0) {
						String trimmedComment = ASMParse.instance.trimComment(comment);
						if (trimmedComment.length()>0) {
							int startLine = lineNum;
							for (++lineNum;lineNum<u.length() && u.code[lineNum].trim().length()==0 && u.comments[lineNum].length()>0;++lineNum) {}
							String[] tempDoc = (String[])ArrayUtil.subarray(u.comments, startLine, lineNum);
							--lineNum;	// Don't skip the line after the comment
							tempDoc = ASMDoc.instance.docify(tempDoc);
							if (docComment!=null && tempDoc!=null) {
								// If previous had type or name, warn that it's being ignored
								if ((docComment[0].length()>0) || (docComment[1].length()>0)) {
									data.getLog().add(file.getName() + "(" + (u.startLineNumber + startLine) + "): Ignoring previous potential doc comment. (type,name) = ("+docComment[0]+","+docComment[1]+")");
								}
								// Otherwise, it's pretty unlikely that it's a doc comment
								docComment = null;
							}
							if (tempDoc!=null) {
								tempDoc[0] = tempDoc[0].toLowerCase();
								if (tempDoc[0].equals("section") || tempDoc[0].equals("segment")) {
									Section newSection = new Section(tempDoc[1],"");
									ASMDoc.instance.addDocumentation(newSection,tempDoc);
									content.add(newSection);
								}
								else if (tempDoc[0].equals("file")) {
									ASMDoc.instance.addDocumentation(file,tempDoc);
								}
								else {
									docComment = tempDoc;
								}
							}
						}
					}
					if (!somethingFound && startUnparsed < 0) {
						startUnparsed = lineNum;
					}
				}
				if (startUnparsed >= 0) {
					content.add(u.getSubrange(startUnparsed, u.length()));
				}
			}
		}
		if (docComment!=null && docComment[0].equals("Section")) {
			Section newSection = new Section(docComment[1],"");
			ASMDoc.instance.addDocumentation(newSection,docComment);
			content.add(newSection);
		}
		data.setContent(content);
	}
}
